#include "Report.h"

using namespace std;
using namespace uhdpbs::db;
using namespace uhdpbs::generated;
using namespace uhdpbs::reports;
using namespace uhdpbs::menu;

namespace uhdpbs {
		namespace reports {
			Receivables::Receivables() {}
			string Receivables::Generate(patient_table& patients) {
				stringstream ssout = stringstream(ios::out);
				//loads payment table from db
				payment_table payments = payment_table(patients.get_connection());
				payments.load();
				//loads visit table from db
				visit_table visits = visit_table(patients.get_connection());
				visits.load();
				//get patient(s)
				for(int cnt = 0; cnt < patients.get_rows()->size(); cnt++) {
					patient_row patient = (patient_row) (*patients.get_rows())[cnt];
					ssout << GenerateReportForPatient(patient, payments, visits);
				}

				payments.save();
				visits.save();

				return ssout.str();
			}

			string Receivables::GenerateReportForPatient(patient_row& patient, payment_table& payments, visit_table& visits) {
				stringstream ssout = stringstream(ios::out);
				float fPaid = CalculateTotalPayments(patient, payments);
				
				ssout << PatientHeader(patient); //display patient info
				ssout << GeneratePayments(patient, payments); // only output unprinted payments, and mark as printed
				ssout << GenerateVisits(patient, visits); // only output unprinted payments, and mark as printed
				ssout << GenerateAgedVisits(patient, visits, fPaid); // for each visit not paid in full print report 0-30, 30-60, 60-90, 90+

				return ssout.str();
			}
			string Receivables::PatientHeader(patient_row& patient) {
				stringstream ssout = stringstream(ios::out);
				//Displays a header per patient
				ssout << "-------------------------------------------------------------------------------" << endl;
				ssout << "ID: " << patient.get_id() << Pad("Name: ", 10) << patient.get_first_name() << " " << patient.get_last_name()
					 << Pad("DOB: ", 10) << patient.get_dob() << endl;
				ssout << "-------------------------------------------------------------------------------" << endl;

				return ssout.str();
			}
			string Receivables::GenerateVisits(patient_row& patient, visit_table& visits) {
				stringstream ssout = stringstream(ios::out);
				
				ssout << "\nUnprinted Visits: " << endl << endl;
				ssout<< Pad("ID", 7) << Pad("Date", 15) << Pad("CPT Code", 15) << Pad("Symptoms", 25) 
					<< Pad("Amount", 10) << endl;
				//get visit(s) information
				for(int cnt = 0; cnt < visits.get_rows()->size(); cnt++) {
					visit_row visit = (visit_row) (*visits.get_rows())[cnt];
					if(patient.get_id() == visit.get_patient_id() && visit.get_is_printed() == "0") {
						float VisitPrice = ConvertStringToFloat(visit.get_price());
						
						ssout << Pad(visit.get_id(), 7);
						ssout << Pad(visit.get_date(), 15);
						ssout << Pad(visit.get_cpt_codes(), 15);
						ssout << Pad(visit.get_symptoms(), 25);
						ssout << PadMoney(visit.get_price(), 10);  
						ssout << endl;

						//has been printed and saves is_printed as "1" (i.e, true)
						visit.set_is_printed("1");
						(*visits.get_rows())[cnt] = visit;
					}
				}

				return ssout.str();
			}
			string Receivables::GeneratePayments(patient_row& patient, payment_table& payments) {
				stringstream ssout = stringstream(ios::out);
				
				ssout << "\nUnprinted Payments: " << endl << endl;
				ssout<< Pad("ID", 7) << Pad("Date", 15) << Pad("Reference", 15) << Pad("Amount", 15) << endl;
				//get payment(s) information
				for(int cnt = 0; cnt < payments.get_rows()->size(); cnt++) {
					payment_row payment = (payment_row) (*payments.get_rows())[cnt];
					if(patient.get_id() == payment.get_patient_id() && payment.get_is_printed() == "0") {
						float PaymentPrice = ConvertStringToFloat(payment.get_amount());
						ssout << Pad(payment.get_id(), 7);
						ssout << Pad(payment.get_date(), 15);
						ssout << Pad(payment.get_reference(), 15);
						ssout << PadMoney(payment.get_amount(), 15); 
						ssout << endl;

						//has been printed and saves is_printed as "1" (i.e, true)
						payment.set_is_printed("1");
						(*payments.get_rows())[cnt] = payment;
					}
				}

				return ssout.str();
			}
			string Receivables::GenerateAgedVisits(patient_row& patient, visit_table& visits, float fPaid) {
				stringstream ssout = stringstream(ios::out);

				float fVisitCostsTotal = 0;
				ssout << "\n";
				ssout << "Aged Visits That Has Not Been Paid: " << endl << endl;
				ssout <<Pad("Visit Date", 15) << Pad("0-30 Days", 15) << Pad("31-60 Days", 15) << Pad("61-90 Days", 15) 
					<< Pad("90+ Days", 15) << endl;
				for(int visitCnt = 0; visitCnt < visits.get_rows()->size(); visitCnt++) {
					visit_row visit = (visit_row) (*visits.get_rows())[visitCnt];

					if(patient.get_id() == visit.get_patient_id()) {
						float fVisitCost = ConvertStringToFloat(visit.get_price());
						fVisitCostsTotal += fVisitCost;

						if(fVisitCostsTotal < fPaid) {
							// do nothing if PAID
						} else {
							ssout << Pad(visit.get_date(), 15);
							int days = CalculateDaysOld(visit.get_date());
							if(days <= 30) {
								ssout << PadMoney(fVisitCost, 15) << endl;  
							} else if(days > 30 && days <= 60) {
								ssout << Pad("", 15) << PadMoney(fVisitCost, 15) << endl;  
							} else if(days > 60 && days <= 90) {
								ssout<< Pad("", 15) << Pad("", 15) << PadMoney(fVisitCost, 15) << endl;  
							} else if(days > 90) {
								ssout << Pad("", 15) << Pad("", 15) << Pad("", 15) << PadMoney(fVisitCost, 15) << endl;  
							}
						}
					}
				}

				ssout << "\n" << "\n";
				ssout << Pad("Total Visit Costs: ", 20) <<  PadMoney(fVisitCostsTotal, 15) << endl;
				ssout << Pad("Total Payments: ", 20) <<  PadMoney(fPaid, 15) << endl;
				ssout << Pad("Total Due: ", 20) <<  PadMoney(fVisitCostsTotal - fPaid, 15) << endl;
				ssout << endl;
				ssout << endl;


				return ssout.str();
			}

			float Receivables::CalculateTotalPayments(patient_row& patient, payment_table& payments) {
				float paymentsTotal = 0;
				//get payment(s)
				for(int cnt = 0; cnt < payments.get_rows()->size(); cnt++) {
					payment_row payment = (payment_row) (*payments.get_rows())[cnt];
					//calculate sum of payment(s)
					if(patient.get_id() == payment.get_patient_id())
						paymentsTotal += atof(payment.get_amount().c_str());
				}

				return paymentsTotal;
			}
			int Receivables::CalculateDaysOld(time_t date) {
				time_t now = time(NULL); // get time of now
				double seconds_in_day = 60 * 60 * 24;
				double age_in_days = difftime(date, now) / seconds_in_day;
				return (int) abs(age_in_days); 
			}
			int Receivables::CalculateDaysOld(std::string date) {
				return CalculateDaysOld(ConvertToTimeT(date));
			}
			float Receivables::ConvertStringToFloat(string val) {
				float payment_Fvalue;

				payment_Fvalue = atof(val.c_str());

				return payment_Fvalue;
			}

			time_t Receivables::ConvertToTimeT(string val) {
				// Must be in YYYY-MM-DD format
				// Must be in YYYY.MM.DD format
				// Must be in YYYY/MM/DD format
				//            0123456789
				if(val.length() != 10)
					return NULL; // ERROR: date not long enought;

				string sYear = val.substr(0, 4);
				int iYear = (int) ConvertStringToFloat(sYear);
				string sMonth = val.substr(5, 2);
				int iMonth = (int) ConvertStringToFloat(sMonth);
				string sDay = val.substr(8, 2);
				int iDay = (int) ConvertStringToFloat(sDay);

				tm ret = tm();
				ret.tm_mon = iMonth;
				ret.tm_mday = iDay;
				ret.tm_year = iYear - 1900;
				ret.tm_hour = 0;
				ret.tm_min = 0;
				ret.tm_sec = 0;

				tm* pTm = &ret;
				time_t ttRet = mktime(pTm);
				return ttRet;
			}

			std::string Receivables::Pad(std::string val, int wide, bool dLeftPad) {
				int iCharsNeeded = wide - val.length();
				string ret = "";
				if(iCharsNeeded <= 0) {
					// val is longer than column, truncate
					for(int cnt = 0; cnt < wide; cnt++)
						ret += val[cnt];
				} else {
					// needs white added infront
					if(!dLeftPad)
						ret += val;
					
					for(int cnt = 0; cnt < iCharsNeeded; cnt++)
						ret += " ";
					
					if(dLeftPad)
						ret += val;
				}

				return ret;
			}

			std::string Receivables::Pad(float val, int wide, bool dLeftPad) {
				stringstream ssout = stringstream(ios::out);
				ssout << val;
				return Pad(ssout.str(), wide, dLeftPad);
			}

			std::string Receivables::PadMoney(std::string val, int wide, bool dLeftPad) {
				return PadMoney(ConvertStringToFloat(val), wide, dLeftPad);
			}
			std::string Receivables::PadMoney(float val, int wide, bool dLeftPad) {
				stringstream ssout = stringstream();
				ssout << fixed << setprecision(2) << val;
				return Pad("$ " + ssout.str(), wide, dLeftPad);
			}

			void Receivables::AgedReceivablesMenu(connection& dbConn){
				int iStartPosition = 1;
				while(true) {
					Menu mnu = Menu("Aged Receivables Options", " > ", "");

					mnu.AddOption("Print to screen for a single patient");
					mnu.AddOption("Print to screen for all patients");
					mnu.AddOption("Print out for a single patient");
					mnu.AddOption("Print out for all patients");

					mnu.AddOption("Exit to Main Menu");

					int iSelection = mnu.Play(iStartPosition);
					VP_ClearScreen();
					switch(iSelection) {
						case 1: SinglePatientReport(dbConn); break;
						case 2: AllPatientsReport(dbConn); break;
						case 3: SinglePatientPrintOut(dbConn); break;
						case 4: AllPatientsPrintOut(dbConn); break;
						case 5: 
							return;
						default: 
							cout << "Invalid selection" << endl;
							cout << "Press enter to continue...";
							cin.get();
							break;
						}
						iStartPosition = iSelection;
					}
				}

			void Receivables::SinglePatientReport(connection& dbConn){
				VP_ClearScreen();
				cout<<"Enter paitient ID: ";
				string sPatientId = "";
				cin >> sPatientId;
				//creates table for only specific id that was entered
				patient_table pt = patient_table(&dbConn);
				patient_row pr = pt.create_new_row();
				pr.set_id(sPatientId);
				pt.get_rows()->push_back(pr);
				pt.load();

				Receivables Sprt = Receivables();
				cout << Sprt.Generate(pt);
				cout << "\nPress any key to return to the previous menu...";
				cin.get();cin.get();
			}
			void Receivables::SinglePatientPrintOut(connection& dbConn){
				VP_ClearScreen();
				cout<<"Enter paitient ID: ";
				string sPatientId = "";
				cin >> sPatientId;
				//creates table for only specific id that was entered
				patient_table pt = patient_table(&dbConn);
				patient_row pr = pt.create_new_row();
				pr.set_id(sPatientId);
				pt.get_rows()->push_back(pr);
				pt.load();

				Receivables OSrpt = Receivables();
				ofstream ReportFile("SinglePaitentsReport.txt");
				if (ReportFile.is_open()){
					ReportFile << OSrpt.Generate(pt);
					ReportFile.close();
				}
				cout << "\n\nThe report has been printed out to 'SinglePatientsReport.txt'"<<endl;
				cout << "\nPress any key to return to the previous menu...";
				cin.get(); cin.get();
			}

			void Receivables::AllPatientsReport(connection& dbConn){
				patient_table pt = patient_table(&dbConn);
				pt.load();

				Receivables rpt = Receivables();
				cout << rpt.Generate(pt);
				cout << "\nPress any key to return to the previous menu...";
				cin.get();
			}
			void Receivables::AllPatientsPrintOut(connection& dbConn){
				patient_table pt = patient_table(&dbConn);
				pt.load();
				Receivables Orpt = Receivables();
				ofstream ReportFile("AllPaitentsReport.txt");
				if (ReportFile.is_open()){
					ReportFile << Orpt.Generate(pt);
					ReportFile.close();
				}
				cout << "The report has been printed out to 'AllPatientsReport.txt'"<<endl;
				cout << "\nPress any key to return to the previous menu...";
				cin.get();
			}
	}
}